
import sys
print(sys.argv)

import znutils
#-----------------------------
# install modbus libs
# pip3 install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple  pymodbus==2.5.3  --低版本 
#-----------------------------
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import serial, serial.tools.list_ports
from fastapi import FastAPI
from datetime import datetime
from pydantic import BaseModel, PositiveInt
import uvicorn
import threading
import time
import datetime
import json

class TZnDecoder:
    def __init__(self):
        pass
    @staticmethod
    def decode(var , regs , exInfo=None):
        status = 0
        value = 0
        try:
            funType = var['funType']
            if funType=='Bit':
                status , value = TZnDecoder.decodeBit(var , regs , exInfo)
            elif funType=='U16':
                status , value = TZnDecoder.decodeU16(var , regs , exInfo)
            elif funType=='I16':
                status , value = TZnDecoder.decodeI16(var , regs , exInfo)
            elif funType=='U32':
                status , value = TZnDecoder.decodeU32(var , regs , exInfo)
            elif funType=='I32':
                status , value = TZnDecoder.decodeI32(var , regs , exInfo)
            elif funType=='I32LH':
                status , value = TZnDecoder.decodeI32LH(var , regs , exInfo)
            elif funType=='U64':
                status , value = TZnDecoder.decodeU64(var , regs , exInfo)
            elif funType=='I64':
                status , value = TZnDecoder.decodeI64(var , regs , exInfo)
            elif funType=='U32Time':
                status , value = TZnDecoder.decodeU32Time(var , regs , exInfo)
            elif funType=='PGU':
                status , value = TZnDecoder.decodePgu(var , regs , exInfo)

            if status == 1:
                rate = var['rate']
                if not rate == 1 :
                    value = value * 1.0 * rate
                rate1 = var['rate1']
                if not rate1 == 1 :
                    value = value * 1.0 * rate1
            else:
                print('decode error:' , var , regs)
        except Exception as er:
            print(er)
        return  status , value
    @staticmethod
    def decodeBit(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            bitStart = var['bitStart']
            mask = 0x01 << bitStart
            v1 = regs[0] & mask
            if v1==0 :
                res = 0
            else:
                res = 1
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU16(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            res = regs[0]
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI16(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            v = regs[0]
            if v< 0x7fff:
                res = v
            else:
                res = -1*( (v ^ 0xffff) + 1 )
                #res =  -1 * ( (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU32(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            vH = regs[0]
            vL = regs[1]
            res = (vH << 16 ) + vL
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI32(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            vH = regs[0]
            vL = regs[1]
            v = (vH << 16 ) + vL
            if vH < 0x7fff :
                res = v
            else:
                res = -1 * ( ( v ^ 0xffffffff) +1  )
                #res =  -1 * (  (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI32LH(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            vL = regs[0]
            vH = regs[1]
            v = (vH << 16 ) + vL
            if vH < 0x7fff :
                res = v
            else:
                res = -1 * ( ( v ^ 0xffffffff) +1  )
                #res =  -1 * (  (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU64(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            v0 = regs[2]
            v1 = regs[3]
            v2 = regs[0]
            v3 = regs[1]
            v = v0<<48 + v1<<32 + v2<<16 + v3
            res = v
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI64(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            v0 = regs[2]
            v1 = regs[3]
            v2 = regs[0]
            v3 = regs[1]
            v = v0<<48 + v1<<32 + v2<<16 + v3
            if v0 < 0x7fff:
                res = v
            else:
                res = -1 * ( ( v ^ 0xffffffffffffffff) +1  )
                #res =  -1 * (  (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodePgu(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            bitStart = var['bitStart']
            bitLen = var['bitLen']
            if bitLen == 1 :
                res = regs[bitStart]
            elif bitLen == 2 :
                res = (regs[bitStart]<<16) + regs[bitStart+1]
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU32Time(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            rH = regs[0]
            rL = regs[1]
            varValue = (rH << 16 ) + rL
            varValue = varValue - 8 * 3600
            res =  varValue
            #varDisp = (datetime.datetime.fromtimestamp(varValue)).strftime("%Y-%m-%d %H:%M:%S")
            status = 1
        except Exception as er:
            print(er)
        return  status , res
class TZnTer:
    def __init__(self):
        self.settings = {}
        self.db = znutils.TPyDB()
        self.tdb = znutils.TPyInfluxDB()
        self.mqclient = znutils.TMq()
        self.info = {
            "bus":{},
            "busUnit":{},
            "task":{}, 
            "var":{},
        }
        self.buses = {}
        self.regs = {}
        self.vars = {}
    def load(self):
        try:
            # set settings
            terCode = self.settings['terminal']['terCode'];
            dbSettings = self.settings['db']
            self.db.setSettings(**dbSettings)
            tdbSettings = self.settings['tdb']
            self.tdb.setSettings(**tdbSettings)
            mqSettings = self.settings['mq']
            self.mqclient.setSettings(**mqSettings)

            # load info
            #sql = "select * from v_variable where protocolType='modbusRtu'"
            sql = "select * from v_variable where terCode='"+terCode+"'"
            print(sql)
            rs = self.db.query(self.settings['db']['code'], sql, {}) 
            busFields = ['busKey','busCode','busName','busDesc','protocolType','com_port','com_baudrate','com_stopbits','com_parity','com_bytesize','tcp_host','tcp_port']
            busUnitFields = ["busUnitCode" , "unitId" , "unitDesc" , "devCode" , "busKey"]
            taskFields=['taskKey' , 'regStart'  ,'busKey' , 'busUnitKey' , 'unitId'  , 'regLen' ,'collectPeriod']
            for item in rs['recordset']:
                try:
                    busKey = item['busKey']
                    busUnitKey = item['busUnitKey']
                    taskKey = item['taskKey']
                    varKey = item['varKey'] 
                    #  bus 
                    if busKey not in self.info['bus'].keys():
                        self.info['bus'][busKey] = {
                            "tasks":[],
                            'vars':[]
                        }
                    for feild in busFields:
                        self.info['bus'][busKey][feild] = item[feild]
                    #  busUnit
                    if busUnitKey not in self.info['busUnit'].keys():
                        self.info['busUnit'][busUnitKey] = {}
                    for field in busUnitFields:
                        self.info['busUnit'][busUnitKey][field] = item[field]
                    if taskKey not in self.info['task'].keys():
                        self.info['task'][taskKey] = {
                            "regLen": item['regLen'],
                            "regs":[],
                            "vars":[],
                            'lastTime':0 
                        }
                    for field in taskFields:
                        if field == 'regLen':
                            regLen = item['regLen']
                            if regLen > self.info['task'][taskKey][field]:
                                self.info['task'][taskKey][field] = regLen
                        else:                        
                            self.info['task'][taskKey][field] = item[field]
                    if varKey not in self.info['var'].keys():
                        item['varValues'] = {
                            'c':0,
                            'vo':0 ,
                            'd': '',
                            'v': 0,
                            't': 0,
                            'rs': []
                        }
                        self.info['var'][varKey] = item
                        self.info['task'][item['taskKey']]["vars"].append(item)
                        self.info['bus'][item['busKey']]['vars'].append(item)
                except Exception as eer:
                    print(eer)
            for t in self.info['task'].values():
                t['regs']  = list(range(t['regStart'] , t['regStart'] + t['regLen']))
                self.info['bus'][t['busKey']]['tasks'].append(t)

            for b in self.info['bus'].values():
                self.buses[b['busKey']] = TZnBus(b, self)
            
            print('load objects info')
        except Exception as er:
            print(er)
    
    def startBuses(self):
        try:
            self.load()
            for bus in self.buses.values():
                print('start bus' , bus)
                bus.asyncStart()
        except Exception as er:
            print(er)

    def updateModbusVars(self , vars , regs):
        try:
            changedVars =[]
            for var in vars :
                try:
                    status , value    = TZnDecoder.decode( var , regs)
                    if status == 1 :
                        v0 = var['varValues']['v']
                        t = var['varValues']['t']
                        nt = time.time()
                        flag = (( nt - t ) > 15*60 ) or (v0 != value)
                        if flag :
                            if var['decLen']>0:
                                flag = self.variableChanged(var , value )

                        if flag :
                            var['varValues']['c'] = 1
                            var['varValues']['t'] = nt
                            var['varValues']['v'] = value
                            var['varValues']['r'] = regs

                            varUnit = var['varUnit']
                            decLen = var['decLen']
                            funType = var['funType']
                            dispVar = str(value)
                            if funType =='Bit':
                                dispVar = 'ON' if  value== 1 else 'OFF'
                            elif var['funType'] == 'U32Time':
                                dispVar = (datetime.datetime.fromtimestamp(value)).strftime("%Y-%m-%d %H:%M:%S")

                            if decLen > 0 :
                                dispVar = ('%.'+str(decLen)+'f') % value

                            if not varUnit == '':
                                dispVar = dispVar + ' ' + varUnit
                            #if len(dispVar) > 14 :
                            #    print(var)
                            var['varValues']['d']= dispVar

                            changedVars.append(var)
                except Exception as eer:
                    print(eer)
            if len(changedVars)>0:
                self.notify(changedVars)
        except Exception as er:
            print(er)

    def getVarTDBData(self , varObj):
        res =""
        try:
            tabname = "varZniot2024"
            tags= {
                "buseCode": varObj['busCode'] ,
                'unitId':varObj['unitId'],
                'devCode':varObj['devCode'],
                'varId':varObj['varId']
            }
            fields = [  x+'='+str(tags[x]) for x in tags.keys()]
            tm = str(int(1000*1000*1000*varObj['varValues']['t']))
            v = str(int(varObj['varValues']['v']))
            res = tabname+","+",".join(fields)+' v='+v+' '+tm
        except Exception as er:
            print(er)
        return  res
    def getVarInfo(self , varObj):
        res = {
            'id': varObj['varId'],
            'dc':varObj['devCode'],
            'l':varObj['varDesc'],
            'v':varObj['varValues']['v'],
            'd':varObj['varValues']['d'],
            't':varObj['varValues']['t'],
            #'r':varObj['varValues']['r'],
            #'rn':varObj['varValues']['rn'],
            'bi':varObj['bitStart']
        }
        return  res

    def getVariableDebug(self , varId):
        res={
            "varId": varId,
            "regs":[]
        }
        try:
            varObj = self.info['var']['var$'+str(varId)]
            res['varInfo'] = varObj
            regStar = varObj['regStart']
            regLen = varObj['regLen']
            regHead = varObj['regKeyHead']
            for r in range(regStar , regStar+ regLen ):
                rk = regHead + str(r)
                regObj = self.dicts.register[rk]
                res['regs'].append(regObj)
        except Exception as er:
            print(er)
        return res


    def notify(self , changedVars):
        try:
            if len(changedVars)>0:
                msgDict = {}
                for varObj in changedVars:
                    #print(varObj)
                    varKey = varObj['varKey']
                    msgDict[varKey] = varObj['varValues']['v']
                msg = json.dumps(msgDict)
                self.mqclient.sendMsg("mq/zniot2024/var" , msg)
                msg = None
        except Exception as er:
            print(er)

    def save(self , buskey):
        try:
            vars = self.info['bus'][buskey]["vars"]
            changedVars = [ x for x in vars if x['varValues']['c']==1]
            postList =[]
            for var in changedVars:
                postList.append( self.getVarTDBData(var) )
            postData = "\n".join(postList)
            self.tdb.write2TDB( self.settings['tdb']['code'] , postData, '')
            for var in changedVars:
                var['varValues']['c'] = 0
        except Exception as er:
            print(er)

    def variableChanged(self , var , newValue , timeout= 0 , valueThreshold= 0.005):
        res = False
        try:
            v0 = var['varValues']['v']
            t0 = var['varValues']['t']
            tn = time.time()
            if tn-t0> timeout:
                res = True
            elif v0==0 and (not newValue==0):
                res = True
            else:
                dltPer = abs(1.0*abs(v0) - abs(newValue)) / abs(v0)
                res = dltPer >= valueThreshold
        except Exception as er:
            print(er)
        return  res

class TZn645:
    def __init__(self):
        self.comPort = 'COM8'
        self.comBaudrate = 1200
        self.comParity = 'E'  # serial.PARITY_EVEN ,
        self.comTimeout = 2
        self.serial = serial.Serial()

    def isOpen(self):
        res = False
        try:
            if self.serial != None:
                res = self.serial.isOpen()
        except Exception as er:
            print(er)
        return res

    def open(self):
        try:
            if not self.isOpen():
                self.serial = serial.Serial(
                    self.comPort, self.comBaudrate, parity=self.comParity, timeout=self.comTimeout
                )
        except Exception as er:
            print(er)
        return self.isOpen()

    def close(self):
        try:
            if self.serial != None:
                if self.serial.isOpen():
                    self.serial.close()
                self.serial = None
        except Exception as er:
            print(er)

    def readValue(self, address, dataFlag):
        status = 0
        res = ''
        try:
            sendBytes = self.getSendBytes(address, dataFlag)
            self.serial.write(sendBytes)
            resBytes = self.serial.readline()
            resBytes = resBytes[resBytes.index(0x68):]

            addrBytes = ["{:0>2d}".format((x // 16) * 10 + x % 16) for x in resBytes[1:7]]
            addrBytes.reverse()
            addr = ''.join(addrBytes)

            dataBytes = ["{:0>2d}".format(int(hex(x - 0x33).replace('0x', ''))) for x in resBytes[14:-2]]
            dataBytes.reverse()
            res = "".join(dataBytes)

            status = 1
        except Exception as er:
            print(er)
        return status, res

    def getSendBytes(self, address, dataFlag):
        res = []
        try:
            res.append(0x68)
            for i in range(6):
                pos = (6 - (i + 1)) * 2
                addr = int(address[pos:pos + 2])
                addr = (addr // 10) * 16 + addr % 10
                res.append(addr)
            res.append(0x68)
            res.append(0x11)
            res.append(0x04)
            for i in range(4):
                di = int(dataFlag[i]) + 0x33
                res.append(di)
            c = 0
            for i in res:
                c = c + i
                c = c % 256
            res.append(c)
            res.append(0x16)
        except Exception as er:
            print(er)
        return bytearray(res)


class TZnBus:
    def __init__(self , busInfo , terminal):
        self.busInfo = {}
        self.ter = TZnTer()
        self.busInfo = busInfo
        self.ter = terminal 
        self.client = None
        self.inputThread = None
        self.actived = False

    def asyncStart(self):
        try:
            if self.busInfo['protocolType']  == 'modbusRtu':
                self.inputThread = threading.Thread( target=self.inputRegister_modbus , daemon=True )
            if self.busInfo['protocolType']  == '645':
                self.inputThread = threading.Thread( target=self.inputRegister_645 , daemon=True )
            if self.busInfo['protocolType']  == 'Timer':
                self.inputThread = threading.Thread( target=self.inputRegist_timer , daemon=True )

            self.inputThread.start()
        except Exception as er:
            print(er)
    def disConnnect_modbus(self):
        try:
            if self.actived:
                self.actived = False
            if self.client!=None:
                try:
                    self.client.close()
                except Exception as ee:
                    print(ee)
                self.client = None
        except Exception as er:
            print(er)
    def connect_modbus(self):
        res = False
        try:
            self.disConnnect_modbus()
            comPort = self.busInfo['com_port']
            comBaudrate = self.busInfo['com_baudrate']
            comStopBit = self.busInfo['com_stopbits']
            comParity = self.busInfo['com_parity']
            comDataBit = self.busInfo['com_bytesize']
            client = ModbusClient(
                method="rtu" ,
                port= comPort , baudrate=comBaudrate , stopbits=comStopBit, parity= comParity, bytesize=comDataBit ,
                strict= False,
                timeout= 2
                )
            self.client = client
            self.actived = True
            res = True
        except Exception as er:
            print(er)
        return  res
    def inputRegister_modbus(self):
        try:
            self.connect_modbus()
            while self.actived :
                try:
                    for task in self.busInfo['tasks']:
                        try:
                            lastTime = task['lastTime']
                            collectPeriod = task['collectPeriod']
                            regs = []
                            changedVars = []
                            nowTm = time.time()
                            dltTm = nowTm - lastTime
                            if dltTm>  collectPeriod :
                                task['lastTime'] = nowTm
                                regStart = task['regStart']
                                regLen = task['regLen']
                                unitId = task['unitId']
                                try:
                                    if regStart in [ 0 , 1000]:
                                        time.sleep(0.5)
                                    elif regStart in [35300,35304]:
                                        time.sleep(1)
                                        print('reg 3530X')
                                    result = self.client.read_holding_registers(regStart, regLen, unit=unitId)
                                    if result.isError():
                                        print('collect faild', regStart, result)
                                        time.sleep(1)
                                    else:
                                        vars = task['vars']
                                        regs = list(result.registers)
                                        self.ter.updateModbusVars(vars , regs)
                                except Exception as erInput:
                                    print(erInput)
                            else:
                                time.sleep(0.1)
                        except Exception as eer :
                            print(eer)
                    self.ter.save(self.busInfo['busKey'])
                except Exception as ee:
                    print(ee)
            self.disConnnect_modbus()
        except Exception as er:
            print(er)
    def inputRegister_645(self):
        try:
            print('start 645 bus')
            if self.client==None:
                self.client = TZn645()
            self.client.close()
            self.client.comPort = self.busInfo['com_port']
            self.client.comBaudrate = self.busInfo['com_baudrate']
            self.client.comParity = self.busInfo['com_parity']
            self.client.open()
            if self.client.isOpen():
                print('connect to 645 bus' , self.client.comPort)
                self.actived = True
                while self.actived:
                    try:
                        changedVars = []
                        for var in self.busInfo['vars']:
                            try:
                                addr = var['devCode']
                                dataFlag = var['varCode']
                                status , varData = self.client.readValue( addr , dataFlag )
                                print( status , varData)
                                if status == 1 :
                                    rate = var['rate']
                                    rate1 = var['rate1']
                                    decLen = var['decLen']
                                    unit = var['varUnit']
                                    tn = time.time()
                                    value = float(varData)
                                    if not rate == 1:
                                        value = value * rate
                                    if not rate1 == 1:
                                        value = value * rate1
                                    changedFlag = self.ter.variableChanged(var , value )

                                    if changedFlag :
                                        dispValue =  ('%.'+str(decLen)+'f') % value
                                        if not unit == '':
                                            dispValue = dispValue + ' ' + unit
                                        var['varValues']['v'] = value
                                        var['varValues']['d'] = dispValue
                                        var['varValues']['t'] = tn
                                        #var['varValues']['r'] = regs
                                        self.ter.notify([var])
                                        changedVars.append(var)
                            except Exception as eer:
                                print(eer)
                        if len(changedVars)>0:
                            self.ter.save(self.busInfo['busKey'])
                    except Exception as ee:
                        break
                        print(ee)
                self.client.close()

        except Exception as er:
            print(er)

    def inputRegist_timer(self):
        try:
            print('start timer bus:')
            print(self.busInfo)
        except Exception as er:
            print(er)

terConfig = {
    "terminal":{
        "terCode":"ter_liyanng_01"
    },
    "server":{
        "port":18000
    },
    "db":{
        "code": "zn_v5",
        "dbType":"mssql",
        "host": "meeting.xmgkfw.com",
        "port": 42049,
        "uid": "sa",
        "pwd": "Hhuc1115",
        "db": "zn_v5",
        "sqlPath":"D:/project/zniot2024/terminal/server/sqls"
    },
    "tdb":{
        "code":"zniot_influxdb",
        "host":"meeting.xmgkfw.com",
        "port":42036 ,
        "token":"Basic cm9vdDpoaHVjMTExNQ==",
        "db":"pcs2023"
    },
    "mq":{
        "host":"hh.hhdata.cn" ,
        "port":31274,
        "uid":"admin",
        "pwd":"hhuc1115",
        "subscribeTopics":["mq/zniot2024"]
    }
}


app = FastAPI()


@app.get("/zn")
async def znRoot():
    return {
        "port": terConfig['server']['port']
    }


@app.get("/zn/start")
async def znRoot():
    ter.startBuses()
    return {
        "ac": "start"
    }


@app.get("/zn/vars")
async def znGetVars():
    res = [ter.getVarInfo(x) for x in ter.info['var'].values()]
    return res


@app.get("/zn/varDebug")
async def znGetVarDebug(varId: int):
    res = ter.getVariableDebug(varId)
    return res


ter = TZnTer()
terCode = 'ter_liyanng_01'
if len(sys.argv) > 1:
    terCode = sys.argv[1]
terConfig['terminal']['terCode'] = terCode
ter.settings = terConfig

ter.load()
ter.mqclient.start(True)


if __name__ == "__main__":

    _port = terConfig['server']['port']
    moduleName = "znModbuTer"
    uvicorn.run(moduleName+":app", host="0.0.0.0" , port=_port,  reload=False)

